package visitor;

import symboltable.ClassTable;
import symboltable.Symbol;
import java.util.*;
import symboltable.*;
import syntaxtree.*;

public class SymbolTableVisitor implements TypeVisitor {

	private SymbolTable symbolTable = new SymbolTable();
	private ClassTable classTable = null;	
	
	@Override
	public Type visit(Program n) {
		n.m.accept(this);
		for ( int i = 0; i < n.cl.size(); i++ ) {
	        n.cl.elementAt(i).accept(this);
	    }
		print();
		return null;
	}
	
	@Override
	public Type visit(MainClass n) {
		n.i1.accept(this); 
		classTable = symbolTable.addClass(Symbol.symbol(n.i1.toString()));
		n.i2.accept(this); 
	    n.s.accept(this);
	   
		return null;
	}

	@Override
	public Type visit(ClassDeclSimple n) {
		n.i.accept(this);
		classTable = symbolTable.addClass(Symbol.symbol(n.i.toString()));
		for ( int i = 0; i < n.vl.size(); i++ ) {
			classTable.addFieldsTable(n.vl.elementAt(i).i, n.vl.elementAt(i).t); 
	        n.vl.elementAt(i).accept(this);
	    }
	    for ( int i = 0; i < n.ml.size(); i++ ) {	    	
                classTable.addMethod(n.ml.elementAt(i).i, n.ml.elementAt(i).t); 
	        n.ml.elementAt(i).accept(this);
	    }
		return null;
	}

	@Override
	public Type visit(ClassDeclExtends n) {
		n.i.accept(this); 
		classTable = symbolTable.addClass(Symbol.symbol(n.i.toString())); 
	    n.j.accept(this); 
	    symbolTable.addHeritage(Symbol.symbol(n.i.toString()), Symbol.symbol(n.j.toString()));
	    populateHeritagesTable();
	    for ( int i = 0; i < n.vdl.size(); i++ ) {
	    	classTable.addFieldsTable(n.vdl.elementAt(i).i, n.vdl.elementAt(i).t); 
	        n.vdl.elementAt(i).accept(this);
	    }
	    for ( int i = 0; i < n.mdl.size(); i++ ) {	    	                       
                classTable.addMethod(n.mdl.elementAt(i).i, n.mdl.elementAt(i).t);
	        n.mdl.elementAt(i).accept(this);
	    }
		return null;
	}

	@Override
	public Type visit(VarDecl n) {
		n.t.accept(this); 
	    n.i.accept(this); 
		return null;
	}

	@Override
	public Type visit(MethodDecl n) {
		n.t.accept(this);
	    n.i.accept(this); 
	   
	    
	    for ( int i = 0; i < n.fl.size(); i++ ) { 
	    	addParams(n.fl.elementAt(i).i, n.fl.elementAt(i).t);
	        n.fl.elementAt(i).accept(this);
	    }
	    for ( int i = 0; i < n.vl.size(); i++ ) { 
	    	addLocals(n.vl.elementAt(i).i, n.vl.elementAt(i).t);
	        n.vl.elementAt(i).accept(this);
	    }
	    for ( int i = 0; i < n.sl.size(); i++ ) { 
	        n.sl.elementAt(i).accept(this);
	    }
	    n.e.accept(this); 
		return null;
	}

	@Override
	public Type visit(Formal n) {
		n.t.accept(this); 
	    n.i.accept(this); 
	    addParams(n.i, n.t);
		return null;
	}

	@Override
	public Type visit(IntArrayType n) {
		return n;
	}

	@Override
	public Type visit(BooleanType n) {
		return n;
	}

	@Override
	public Type visit(IntegerType n) {
		return n;
	}

	@Override
	public Type visit(IdentifierType n) {
		return n;
	}

	@Override
	public Type visit(Block n) {
		for ( int i = 0; i < n.sl.size(); i++ ) {
	        n.sl.elementAt(i).accept(this);
	    }
		return null;
	}

	@Override
	public Type visit(If n) {
		n.e.accept(this);
	    n.s1.accept(this);
	    n.s2.accept(this);
		return null;
	}

	@Override
	public Type visit(While n) {
		n.e.accept(this);
	    n.s.accept(this);
		return null;
	}

	@Override
	public Type visit(Print n) {
		n.e.accept(this);
		return null;
	}

	@Override
	public Type visit(Assign n) {
		n.i.accept(this);
	    n.e.accept(this);
		return null;
	}

	@Override
	public Type visit(ArrayAssign n) {
		n.i.accept(this);
	    n.e1.accept(this);
	    n.e2.accept(this);
		return null;
	}

	@Override
	public Type visit(And n) {
		n.e1.accept(this);
	    n.e2.accept(this);
		return null;
	}

	@Override
	public Type visit(LessThan n) {
		n.e1.accept(this);
	    n.e2.accept(this);
		return null;
	}

	@Override
	public Type visit(Plus n) {
		n.e1.accept(this);
	    n.e2.accept(this);
		return null;
	}

	@Override
	public Type visit(Minus n) {
		n.e1.accept(this);
	    n.e2.accept(this);
		return null;
	}

	@Override
	public Type visit(Times n) {
		n.e1.accept(this);
	    n.e2.accept(this);
		return null;
	}

	@Override
	public Type visit(ArrayLookup n) {
		n.e1.accept(this);
	    n.e2.accept(this);
		return null;
	}

	@Override
	public Type visit(ArrayLength n) {
		n.e.accept(this);
		return null;
	}

	@Override
	public Type visit(Call n) {
		n.e.accept(this);
	    n.i.accept(this);
	    for ( int i = 0; i < n.el.size(); i++ ) {
	        n.el.elementAt(i).accept(this);
	    }
		return null;
	}

	@Override
	public Type visit(IntegerLiteral n) {
		return null;
	}

	@Override
	public Type visit(True n) {
		return null;
	}

	@Override
	public Type visit(False n) {
		return null;
	}

	@Override
	public Type visit(IdentifierExp n) {
		return null;
	}

	@Override
	public Type visit(This n) {
		return null;
	}

	@Override
	public Type visit(NewArray n) {
		n.e.accept(this);
		return null;
	}

	@Override
	public Type visit(NewObject n) {
		return null;
	}

	@Override
	public Type visit(Not n) {
		n.e.accept(this);
		return null;
	}

	@Override
	public Type visit(Identifier n) {
		//id = n.s;
		return new IdentifierType(n.s);
	}
	
	private void addParams(Identifier identifier, Type type) {
		if (classTable.getMethodsTable() != null) { 
			classTable.getMethodsTable().getHashmapMethodTable().get(Symbol.symbol(classTable.getMethodsTable().getIdAtual())).addParams(identifier, type);	
	    }
	}
	
	private void addLocals(Identifier identifier, Type type) {
		if (classTable.getMethodsTable() != null) { 
	    	classTable.getMethodsTable().getHashmapMethodTable().get(Symbol.symbol(classTable.getMethodsTable().getIdAtual())).addLocals(identifier, type);
	    }
	}
	
	private void populateHeritagesTable() {
		for (Map.Entry<Symbol, Symbol> hTable : symbolTable.getHeritages().entrySet()) {
			ClassTable c1 = symbolTable.getClass(hTable.getKey()); 
			ClassTable c2 = symbolTable.getClass(hTable.getValue()); 
			c1.getFieldsTable().getHashmapFieldsTable().putAll(c2.getFieldsTable().getHashmapFieldsTable());
			c1.getMethodsTable().getHashmapMethodsTable().putAll(c2.getMethodsTable().getHashmapMethodsTable());
			c1.getMethodsTable().getHashmapMethodTable().putAll(c2.getMethodsTable().getHashmapMethodTable());
		}
	}
	
	private void print() {
		for (Map.Entry<Symbol, ClassTable> sbTable : symbolTable.getSymbolTable().entrySet()) {
			System.out.println("-> Classe: " + sbTable.getKey().toString());
			if (sbTable.getValue().getFieldsTable() != null) {
				System.out.println("  Campos:");
				for (Map.Entry<Symbol, Type> fieldsTable : sbTable.getValue().getFieldsTable().getHashmapFieldsTable().entrySet()) {
					System.out.println("    Símbolo: " + fieldsTable.getKey().toString());
					System.out.println("    Tipo: " + fieldsTable.getValue().toString());
				}
			}
			
			if (sbTable.getValue().getMethodsTable() != null) {
				System.out.println("  Método:");
				for (Map.Entry<Symbol, Type> methodsTable : sbTable.getValue().getMethodsTable().getHashmapMethodsTable().entrySet()) {
					System.out.println("    Símbolo: " + methodsTable.getKey().toString());
					System.out.println("    Tipo: " + methodsTable.getValue().toString());
						if (sbTable.getValue().getMethodsTable().getHashmapMethodTable().containsKey(methodsTable.getKey())) {
							MethodTable methodTable = sbTable.getValue().getMethodsTable().getHashmapMethodTable().get(methodsTable.getKey());
							
							if (methodTable.getParamsTable() != null) {
								System.out.println("      Parametros: ");
								for (Map.Entry<Symbol, Type> paramsTable : methodTable.getParamsTable().getHashmapParamsTable().entrySet()) {
									System.out.println("        Simbolo: " + paramsTable.getKey().toString());
									System.out.println("        Tipo: " + paramsTable.getValue().toString());
								}
							}
							
							if (methodTable.getLocalsTable() != null) {
								System.out.println("      Local: ");
								for (Map.Entry<Symbol, Type> localsTable : methodTable.getLocalsTable().getHashmapLocalsTable().entrySet()) {
									System.out.println("        Simbolo: " + localsTable.getKey().toString());
									System.out.println("        Tipo: " + localsTable.getValue().toString());
								}
							}
						}
				}
			}
		}
	}
		
	public SymbolTable getSymbolTable() {
		return symbolTable;
	}

	public void setSymbolTable(SymbolTable symbolTable) {
		this.symbolTable = symbolTable;
	}

}
